home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Shareware Grab Bag
/
Shareware Grab Bag.iso
/
011
/
treedupl.arc
/
TREEDUPL.PAS
< prev
next >
Wrap
Pascal/Delphi Source File
|
1986-09-05
|
59KB
|
1,771 lines
PROGRAM TreeDuplicate; {008}
CONST
VersionIdentification = '2.0';
(***********************************************************************
This software has been placed into the public domain by Digital
Equipment Corporation.
DISCLAIMER:
The information herein is subject to change without notice and should
not be construed as a commitment by Digital Equipment Corporation.
Digital Equipment Corporation assumes no responsibility for the use or
reliability of this software. This software is provided "as is,"
without any warranty of any kind, express or implied. Digital Equipment
Corporation will not be liable in any event for any damages including
any loss of data, profit, or savings, claims against the user by any
other party, or any other incidental or consequential damages arising
out of the use of, or inability to use, this software, even if Digital
Equipment Corporation is advised of the possibility of such damage.
DEFECT REPORTING AND SUGGESTIONS:
Please send reports of defects or suggestions for improvement directly
to the author:
Brian Hetrick
Digital Equipment Corporation
110 Spit Brook Road ZKO1-3/J10
Nashua NH 03062-2698
Do NOT file a Software Performance Report on this software, call the
Telephone Support Center regarding this software, contact your Digital
Field Office regarding this software, or use any other mechanism
provided for Digital's supported and warranted software.
FACILITY:
General user utilities
ABSTRACT:
Duplicates one directory tree into another, attempting not to copy{008}
data if possible. Intended for use as a backup utility using a DEC-
net-DOS virtual disk as the backup medium.
ENVIRONMENT:
MS-DOS compiled with Borland International's TURBO Pascal
AUTHOR: Brian Hetrick, CREATION DATE: 27 May 1986.
MODIFICATION HISTORY:
Brian Hetrick, 27-May-86: Version 1.0
000 - Original creation of module.
Released to Easynet 28-May-86.
Brian Hetrick, 30-May-86: Version 1.1
001 - Attributes on directories were not updated. Cause was that dir-
ectory modification date cannot be set, and IDAttrMatch routine
was testing modification date for directories. Main program
then attempted to replace the target directory, but ReplaceFile
simply returned. Fix is to have IDAttrMatch not look at mod-
ification dates for directories; main program now uses Match-
File to update the attributes.
002 - Included program name and version in banner.
Released to Easynet 30-May-86
Brian Hetrick, 31-May-86: Version 1.2
003 - Introduce hook for having files accumulate on target volume, to
match hook for event logging.
004 - Introduce procedure to check for MS-DOS error, instead of always
explicitly checking low bit of returned Flags register.
005 - Introduce function to form name from root directory, relative
directory, and file in relative directory, rather than always
building directly from volume letter, absolute directory, and
file in absolute directory, as a hook for later permitting root
to be any directory.
006 - Avoid exteraneous copy in ExpandDirectory.
007 - Use only ASCII in message text--replace MCS copyright symbol
with (c) as program may run on IBM PCs without MCS.
Not released to Easynet as no user-visible improvements.
Brian Hetrick, 03-Jun-86: Version 2.0
008 - Change name from VOLCOPY to TREEDUPL, as now will copy trees
rooted at other than the volume root directory.
009 - Use Bela Lubkin's public domain CommandLineArgument routine to
parse the command line.
010 - Deleted copyright notice as program will be submitted to DECUS
program library.
Released to Easynet on 3 June 1986.
Submitted to DECUS Program Library in September 1986.
***********************************************************************)
{.PA}
(*
* INCLUDE FILES:
*)
{$I CLA.PAS} {009}
(*
* LABEL DECLARATIONS:
*)
(*
* CONSTANT DECLARATIONS:
*)
CONST
DOSFunctionChangeAttributes = $43;
DOSFunctionCloseFile = $3E;
DOSFunctionCreateFile = $3C;
DOSFunctionCreateSubDirectory = $39;
DOSFunctionDeleteDirectoryEntry = $41;
DOSFunctionFindMatchFile = $4E;
DOSFunctionGetDTA = $2F;
DOSFunctionOpenFile = $3D;
DOSFunctionReadFromFile = $3F;
DOSFunctionRemoveDirectoryEntry = $3A;
DOSFunctionSetDTA = $1A;
DOSFunctionSetFileDateTime = $57;
DOSFunctionStepThroughDirectory = $4F;
DOSFunctionWriteToFile = $40;
CONST
DirectoryAttrMask = $10; { Attribute bit for directory }
DirectoryEntrySize = 5; { Base length of DirectoryEntry }
FileEntrySize = 20; { Base length of FileEntry }
FileSpecLength = 12; { Length of MS-DOS base name }
PathSpecLength = 127; { Length of MS-DOS path specification }
ReadOnlyAttrMask = $01; { Attribute bit for read-only }
(*
* TYPE DECLARATIONS:
*)
TYPE
FileSpec = STRING [FileSpecLength];
PathSpec = STRING [PathSpecLength];
DirectoryEntryPtr = ^ DirectoryEntry;
DirectoryEntry = RECORD
Next : DirectoryEntryPtr;
Name : PathSpec
END;
FileEntryPtr = ^ FileEntry;
FileEntry = RECORD
Next : FileEntryPtr;
Prev : FileEntryPtr;
Size : REAL;
Time : INTEGER;
Date : INTEGER;
Attr : BYTE;
Name : FileSpec
END;
FileEntryQueue = RECORD
Head : FileEntryPtr;
Tail : FileEntryPtr
END;
RegPack = RECORD
CASE INTEGER OF
0: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags : INTEGER);
1: (AL, AH, BL, BH, CL, CH, DL, DH : BYTE)
END;
(*
* OWN STORAGE:
*)
VAR
Accumulating : BOOLEAN;
Logging : BOOLEAN;
SourceRoot : PathSpec;
TargetRoot : PathSpec;
(*
* TABLE OF CONTENTS:
*)
{.PA}
PROCEDURE ParseCommandLine; {009}
(***********************************************************************{009}
FUNCTIONAL DESCRIPTION: {009}
Parses the program command line. {009}
FORMAL PARAMETERS: {009}
None. {009}
RETURN VALUE: {009}
None. {009}
IMPLICIT INPUTS: {009}
None. {009}
IMPLICIT OUTPUTS: {009}
Accumulating - The BOOLEAN telling whether files on the target are{009}
to be retained if they are not on the source. {009}
Logging - The BOOLEAN telling whether messages informing the user of{009}
actions taken are to be written to the standard output. {009}
SourceRoot - The root directory of the source directory tree. {009}
TargetRoot - The root directory of the target directory tree. {009}
SIDE EFFECTS:
Will Halt the program if an error in the command line is discovered.{009}
***********************************************************************){009}
VAR {009}
CharIndex : INTEGER; {009}
CommandValid : BOOLEAN; {009}
SwitchSense : BOOLEAN; {009}
SwitchText : BigString; {009}
FUNCTION IsPrefix (Str1 : BigString; Str2 : BigString) : BOOLEAN; {009}
VAR {009}
CharIndex : INTEGER; {009}
BEGIN {009}
IF Length (Str1) > Length (Str2) {009}
THEN {009}
IsPrefix := FALSE {009}
ELSE {009}
IsPrefix := Str1 = Copy (Str1, 1, Length (Str1))
END; {009}
PROCEDURE UpCaseString (VAR Str : PathSpec); {009}
VAR {009}
CharIndex : INTEGER; {009}
BEGIN {009}
FOR CharIndex := 1 TO Length (Str) {009}
DO {009}
Str [CharIndex] := UpCase (Str [CharIndex]) {009}
END; {009}
BEGIN {009}
(* {009}
* Get source and destination roots {009}
*) {009}
SourceRoot := CommandLineArgument {009}
('Source directory: ', '/', FALSE); {009}
UpCaseString (SourceRoot); {009}
TargetRoot := CommandLineArgument {009}
('Destination directory: ', '/', FALSE); {009}
UpCaseString (TargetRoot); {009}
(* {009}
* Set defaults {009}
*) {009}
Accumulating := TRUE; {009}
Logging := TRUE; {009}
(* {009}
* Process switches {009}
*) {009}
CommandValid := TRUE; {009}
SwitchText := CommandLineArgument ('', '', TRUE); {009}
WHILE CommandValid AND (Length (SwitchText) > 0) {009}
DO {009}
BEGIN {009}
UpCaseString (SwitchText); {009}
(* {009}
* Get rid of the leading slash {009}
*) {009}
Delete (SwitchText, 1, 1); {009}
IF Length (SwitchText) = 0 {009}
THEN {009}
BEGIN {009}
WriteLn ('Invalid switch: "/"'); {009}
CommandValid := FALSE; {009}
END; {009}
IF CommandValid {009}
THEN {009}
(* {009}
* Check for "NO" prefix {009}
*) {009}
IF Copy (SwitchText, 1, 2) = 'NO' {009}
THEN {009}
BEGIN {009}
SwitchSense := FALSE; {009}
Delete (SwitchText, 1, 2); {009}
IF Length (SwitchText) = 0 {009}
THEN {009}
BEGIN {009}
WriteLn ('Invalid switch: "/NO"'); {009}
CommandValid := FALSE {009}
END {009}
END {009}
ELSE {009}
SwitchSense := TRUE; {009}
IF CommandValid {009}
THEN {009}
BEGIN {009}
(* {009}
* Check for switch names {009}
*) {009}
IF IsPrefix (SwitchText, 'LOG') {009}
THEN {009}
Logging := SwitchSense {009}
ELSE IF IsPrefix (SwitchText, 'ACCUMULATE') {009}
THEN {009}
Accumulating := SwitchSense {009}
ELSE {009}
BEGIN {009}
Write ('Invalid switch: "/'); {009}
IF SwitchSense = FALSE {009}
THEN {009}
Write ('NO'); {009}
WriteLn (SwitchText, '"'); {009}
CommandValid := FALSE {009}
END {009}
END; {009}
IF CommandValid {009}
THEN {009}
SwitchText := CommandLineArgument ('', '', TRUE) {009}
END; {009}
IF NOT CommandValid {009}
THEN {009}
Halt {009}
END; {009}
{.PA}
FUNCTION ErrorReturn {004}
( Registers : RegPack) : BOOLEAN; {004}
(***********************************************************************{004}
FUNCTIONAL DESCRIPTION: {004}
Checks a set of registers returned from the MsDos procedure and de-{004}
termines whether the function completed successfully. {004}
FORMAL PARAMETERS: {004}
Registers - A RegPack expression giving the register values returned{004}
by the MsDos procedure. {004}
RETURN VALUE: {004}
TRUE - The MsDos function failed. {004}
FALSE - The MsDos function succeeded. {004}
IMPLICIT INPUTS: {004}
None. {004}
IMPLICIT OUTPUTS: {004}
None. {004}
SIDE EFFECTS: {004}
None. {004}
***********************************************************************){004}
BEGIN {004}
ErrorReturn := (Registers . Flags AND 1) <> 0 {004}
END; {004}
{.PA}
FUNCTION ConstructFileName {005}
( RootDirectory : PathSpec; {005}
RelativeDirectory : PathSpec; {005}
FileName : FileSpec) : PathSpec; {005}
(***********************************************************************{005}
FUNCTIONAL DESCRIPTION: {005}
Constructs a path specification from a root directory, a relative{005}
directory, and file name by concatenating these elements, separating{005}
them by backslash if there is not already a separator. {005}
FORMAL PARAMETERS: {005}
RootDirectory - A PathSpec expression giving the root directory of{005}
the eventual path specification. {005}
RelativeDirectory - A PathSpec expression giving the directory{005}
relative to RootDirectory of the eventual path specification. {005}
FileName - A FileSpec expression giving the file name of the even-{005}
tual path specification. {005}
RETURN VALUE: {005}
The resultant path specification. {005}
IMPLICIT INPUTS: {005}
None. {005}
IMPLICIT OUTPUTS: {005}
None. {005}
SIDE EFFECTS: {005}
None. {005}
***********************************************************************){005}
CONST {005}
Separator : SET OF CHAR = [':', '\', '/']; {005}
VAR {005}
TempName : PathSpec; {005}
BEGIN {005}
TempName := RootDirectory; {005}
IF (Length (TempName) > 0) AND (Length (RelativeDirectory) > 0) {005}
THEN {005}
IF NOT (TempName [Length (TempName)] IN Separator) {005}
THEN {005}
Insert ('\', TempName, Length (TempName) + 1); {005}
Insert (RelativeDirectory, TempName, Length (TempName) + 1); {005}
IF (Length (TempName) > 0) AND (Length (FileName) > 0) {005}
THEN {005}
IF NOT (TempName [Length (TempName)] IN Separator) {005}
THEN {005}
Insert ('\', TempName, Length (TempName) + 1); {005}
Insert (FileName, TempName, Length (TempName) + 1); {005}
ConstructFileName := TempName {005}
END; {005}
{.PA}
PROCEDURE ExpandDirectory
( RootDirectory : PathSpec; {005}
DirectoryToExpand : DirectoryEntryPtr;
VAR FileQueue : FileEntryQueue);
(***********************************************************************
FUNCTIONAL DESCRIPTION:
Finds and lexicographically sorts the names of all files in a di-
rectory
FORMAL PARAMETERS:
RootDirectory - A PathSpec expression giving the root directory to{005}
which DirectoryName is a relative directory. {005}
DirectoryName - A DirectoryEntryPtr expression pointing to the Di-
recoryEntry describing the directory to be examined
FileQueue - A FileEntryQueue object which is modified to point to a
newly created queue of the names of files in the directory
RETURN VALUE:
None.
IMPLICIT INPUTS:
None.
IMPLICIT OUTPUTS:
None.
SIDE EFFECTS:
Modifies and resets the DTA. This should be observable only by in-
terrupt routines.
Dynamically allocates storage with GetMem.
***********************************************************************)
VAR
FoundPos : BOOLEAN;
FileNameLength : INTEGER;
FileName : FileSpec;
MSDOSBlock : RECORD
Reserved : ARRAY [1..21] OF BYTE;
Attribute : BYTE;
Time : INTEGER;
Date : INTEGER;
SizeLow : INTEGER;
SizeHigh : INTEGER;
Name : ARRAY [1..13] OF CHAR
END;
NextFile : FileEntryPtr;
OldDTA : ^ CHAR;
PrevFile : FileEntryPtr;
Registers : RegPack;
SearchSpec : PathSpec;
ThisFile : FileEntryPtr;
BEGIN
(*
* Initialize the file queue
*)
FileQueue . Head := NIL;
FileQueue . Tail := NIL;
(*
* Save the old DTA
*)
Registers.AH := DOSFunctionGetDTA;
MsDos (Registers);
OldDTA := Ptr (Registers.ES, Registers.BX);
(*
* Set the DTA to be the MS-DOS information block
*)
Registers.AH := DOSFunctionSetDTA;
Registers.DS := Seg (MSDOSBlock);
Registers.DX := Ofs (MSDOSBlock);
MsDos (Registers);
(*
* Find the contents of the directory
*)
SearchSpec := ConstructFileName (RootDirectory, {005}
DirectoryToExpand ^. Name, '*.*'); {005}
SearchSpec [Length (SearchSpec) + 1] := #$00; {005}
Registers.AH := DOSFunctionFindMatchFile;
Registers.DS := Seg (SearchSpec [1]);
Registers.DX := Ofs (SearchSpec [1]);
Registers.CX := $37;
MsDos (Registers);
WHILE NOT ErrorReturn (Registers) {004}
DO
BEGIN
(*
* Extract the file name
*)
FileNameLength := 1;
WHILE MSDOSBlock . Name [FileNameLength] <> #$00
DO
FileNameLength := FileNameLength + 1;
FileNameLength := FileNameLength - 1;
FileName := Copy (MSDOSBlock . Name, 1, FileNameLength);
(*
* Ignore relative directories
*)
IF (FileName <> '.') AND (FileName <> '..')
THEN
BEGIN
(*
* Create a file entry for this file
*)
GetMem (ThisFile, FileEntrySize + FileNameLength);
ThisFile ^. Attr := MSDOSBlock . Attribute;
ThisFile ^. Time := MSDOSBlock . Time;
ThisFile ^. Date := MSDOSBlock . Date;
IF MSDOSBlock . SizeHigh < 0
THEN
ThisFile ^. Size := MSDOSBlock . SizeHigh + 65536.0
ELSE
ThisFile ^. Size := MSDOSBlock . SizeHigh;
ThisFile ^. Size := ThisFile ^. Size * 65536.0;
IF MSDOSBlock . SizeLow < 0
THEN
ThisFile ^. Size := ThisFile ^. Size +
MSDOSBlock . SizeLow + 65536.0
ELSE
ThisFile ^. Size := ThisFile ^. Size +
MSDOSBlock . SizeLow;
ThisFile ^. Name := FileName; {006}
(*
* Insert the newly allocated entry into the sorted queue
*)
NextFile := FileQueue . Head;
PrevFile := NIL;
FoundPos := FALSE;
WHILE NOT FoundPos
DO
BEGIN
IF NextFile = NIL
THEN
FoundPos := TRUE
ELSE
IF NextFile ^. Name > ThisFile ^. Name
THEN
FoundPos := TRUE
ELSE
BEGIN
PrevFile := NextFile;
NextFile := NextFile ^. Next
END
END;
ThisFile ^. Prev := PrevFile;
IF PrevFile = NIL
THEN
FileQueue . Head := ThisFile
ELSE
PrevFile ^. Next := ThisFile;
ThisFile ^. Next := NextFile;
IF NextFile = NIL
THEN
FileQueue . Tail := ThisFile
ELSE
NextFile ^. Prev := ThisFile
END;
(*
* Get the next file in the directory
*)
Registers.AH := DOSFunctionStepThroughDirectory;
MsDos (Registers)
END;
(*
* The directory has been expanded. Reset the DTA
*)
Registers.AH := DOSFunctionSetDTA;
Registers.DS := Seg (OldDTA ^);
Registers.DX := Ofs (OldDTA ^);
MsDos (Registers)
END;
{.PA}
PROCEDURE ExtractDirectories
( CurrentDirectory : DirectoryEntryPtr;
FileQueue : FileEntryQueue;
VAR DirectoryList : DirectoryEntryPtr);
(***********************************************************************
FUNCTIONAL DESCRIPTION:
Examines the contents of the current directory, extracts the full
path names of all subdirectories, and places these subdirectory
names on a queue of pending directories.
FORMAL PARAMETERS:
CurrentDirectory - A DirectoryEntryPtr pointing to a DirectoryEntry
describing the directory whose contents are given by FileQueue.
FileQueue - A FileEntryQueue pointing to a list of FileEntry objects
describing the files in the directory described by Current-
Directory.
DirectoryList - A DirectoryEntryPtr pointing to a list of Directory-
Entry objects. New DirectoryEntry objects are created for the
subdirectories found on the list of FileEntry objects pointed to
by FileQueue, and are placed onto this list.
RETURN VALUE:
None.
IMPLICIT INPUTS:
None.
IMPLICIT OUTPUTS:
None.
SIDE EFFECTS:
Dynamically allocates storage with GetMem.
***********************************************************************)
VAR
DirectoryText : PathSpec;
ThisDirectory : DirectoryEntryPtr;
ThisEntry : FileEntryPtr;
BEGIN
(*
* Scan list backwards, looking for directories
*)
ThisEntry := FileQueue . Tail;
WHILE ThisEntry <> NIL
DO
BEGIN
IF (ThisEntry ^. Attr AND DirectoryAttrMask) <> 0
THEN
BEGIN
(*
* This entry is a directory.
*)
DirectoryText := {005}
ConstructFileName (CurrentDirectory ^. Name, {005}
ThisEntry ^. Name, ''); {005}
GetMem (ThisDirectory, DirectoryEntrySize +
Length (DirectoryText));
ThisDirectory ^. Next := DirectoryList;
ThisDirectory ^. Name := DirectoryText;
DirectoryList := ThisDirectory
END;
ThisEntry := ThisEntry ^. Prev
END
END;
{.PA}
PROCEDURE AdvanceFile
(VAR FileQueue : FileEntryQueue);
(***********************************************************************
FUNCTIONAL DESCRIPTION:
Deletes the first item on a file entry queue.
FORMAL PARAMETERS:
FileQueue - A FileEntryQueue object pointing to a queue of FileEntry
objects. The item pointed at by the Head pointer is deleted,
and the queue is adjusted for this deletion.
RETURN VALUE:
None.
IMPLICIT INPUTS:
None.
IMPLICIT OUTPUTS:
None.
SIDE EFFECTS:
Dynamically frees storage with FreeMem.
***********************************************************************)
VAR
ThisEntry : FileEntryPtr;
BEGIN
(*
* Ensure that there is an item to delete
*)
ThisEntry := FileQueue . Head;
IF ThisEntry <> NIL
THEN
BEGIN
(*
* There is. First, relink the queue around the item
*)
FileQueue . Head := ThisEntry ^. Next;
IF FileQueue . Head = NIL
THEN
FileQueue . Tail := NIL
ELSE
FileQueue . Head ^. Prev := NIL;
(*
* Now free the item's storage
*)
FreeMem (ThisEntry, FileEntrySize + Length (ThisEntry ^. Name))
END
END;
{.PA}
FUNCTION IDAttrMatch
( FileEntry1 : FileEntryPtr;
FileEntry2 : FileEntryPtr) : BOOLEAN;
(***********************************************************************
FUNCTIONAL DESCRIPTION:
Determine whether two files are putatively identical.
Two files are considered to be identical if they have the same name,
same directory attribute, and, in the case of non-directory files,{001}
the same creation/modification date and time and size. NO COM-{001}
PARISON OF THE FILE CONTENTS IS MADE.
FORMAL PARAMETERS:
File1Desc - A FileEntryPtr pointing to a FileEntry object describing
the first of the two files.
File2Desc - A FileEntryPtr pointing to a FileEntry object describing
the second of the two files.
RETURN VALUE:
TRUE - The files are considered to be identical.
FALSE - The files are not considered to be identical.
IMPLICIT INPUTS:
None.
IMPLICIT OUTPUTS:
None.
SIDE EFFECTS:
None.
***********************************************************************)
VAR
Difference : BOOLEAN;
BEGIN
Difference := FALSE;
IF FileEntry1 ^. Name <> FileEntry2 ^. Name
THEN
Difference := TRUE;
IF (FileEntry1 ^. Attr AND DirectoryAttrMask) <>
(FileEntry2 ^. Attr AND DirectoryAttrMask)
THEN
Difference := TRUE;
IF (FileEntry1 ^. Attr AND DirectoryAttrMask) = 0 {001}
THEN {001}
BEGIN {001}
IF FileEntry1 ^. Time <> FileEntry2 ^. Time
THEN
Difference := TRUE;
IF FileEntry1 ^. Date <> FileEntry2 ^. Date
THEN
Difference := TRUE;
IF FileEntry1 ^. Size <> FileEntry2 ^. Size
THEN
Difference := TRUE {001}
END; {001}
IDAttrMatch := NOT Difference
END;
{.PA}
PROCEDURE DeleteFile
( RootDirectory : PathSpec; {005}
CurrentDirectory : DirectoryEntryPtr;
FileInfo : FileEntryPtr);
(***********************************************************************
FUNCTIONAL DESCRIPTION:
Deletes a single file or an entire subdirectory tree. When deleting
an entire subdirectory tree, recurses to the depth of the subdirect-
ory tree.
FORMAL PARAMETERS:
RootDirectory - A PathSpec expression giving the root directory to{005}
which DirectoryName is a relative directory. {005}
CurrentDirectory - A DirectoryEntryPtr expression pointing to a Di-
rectoryEntry object describing the directory in which the file
resides.
FileInformation - A FileEntryPtr expression pointing to a FileEntry
object describing the file to be deleted.
RETURN VALUE:
None.
IMPLICIT INPUTS:
Logging - The BOOLEAN telling whether event logging is currently on.
IMPLICIT OUTPUTS:
None.
SIDE EFFECTS:
None.
***********************************************************************)
VAR
NewDirEntry : DirectoryEntry;
Registers : RegPack;
SubDirQueue : FileEntryQueue;
(*
* A DirectoryEntry is used in place of a PathSpec for the name of
* the single file to be deleted, in order to minimize local stor-
* age requirements. This is important only as this routine is
* recursive.
*)
BEGIN
(*
* If the "file" to be deleted is a directory, delete the entire
* tree rooted there
*)
IF (FileInfo ^. Attr AND DirectoryAttrMask) <> 0
THEN
BEGIN
(*
* Construct a directory entry for the directory
*)
NewDirEntry . Name := {005}
ConstructFileName (CurrentDirectory ^. Name, {005}
FileInfo ^. Name, ''); {005}
(*
* Get contents of directory
*)
ExpandDirectory (RootDirectory, Addr (NewDirEntry), {005}
SubDirQueue);
(*
* Recursively delete the contents of the directory
*)
WHILE SubDirQueue . Head <> NIL
DO
BEGIN
DeleteFile (RootDirectory, Addr (NewDirEntry), {005}
SubDirQueue . Head);
AdvanceFile (SubDirQueue)
END
END;
(*
* Generate the file specification
*)
NewDirEntry . Name := ConstructFileName (RootDirectory, {005}
CurrentDirectory ^. Name, FileInfo ^. Name); {005}
(*
* Put on the trailing NUL for MS-DOS calls
*)
NewDirEntry . Name [Length (NewDirEntry . Name) + 1] := #$00;
(*
* The Read-Only attribute implies that the file cannot be
* deleted. If the Read-Only attribute is on, turn it off.
*)
IF (FileInfo ^. Attr AND ReadOnlyAttrMask) <> 0
THEN
BEGIN
Registers . AH := DOSFunctionChangeAttributes;
Registers . DS := Seg (NewDirEntry . Name [1]);
Registers . DX := Ofs (NewDirEntry . Name [1]);
Registers . CX := FileInfo ^. Attr AND NOT {001}
(ReadOnlyAttrMask OR DirectoryAttrMask); {001}
Registers . AL := 1;
MsDos (Registers);
IF ErrorReturn (Registers) {004}
THEN
BEGIN
WriteLn ('Cannot change attributes on ', NewDirEntry . Name);
Halt
END
END;
(*
* Actually delete the file
*)
IF (FileInfo ^. Attr AND DirectoryAttrMask) <> 0
THEN
Registers . AH := DOSFunctionRemoveDirectoryEntry
ELSE
Registers . AH := DOSFunctionDeleteDirectoryEntry;
Registers . DS := Seg (NewDirEntry . Name [1]);
Registers . DX := Ofs (NewDirEntry . Name [1]);
MsDos (Registers);
IF ErrorReturn (Registers) {004}
THEN
BEGIN
Write ('Cannot delete ');
IF (FileInfo ^. Attr AND DirectoryAttrMask) <> 0
THEN
Write ('directory ');
WriteLn (NewDirEntry . Name);
Halt
END;
(*
* If logging is on, note the deletion
*)
IF Logging
THEN
WriteLn ('Deleted ', NewDirEntry . Name)
END;
{.PA}
PROCEDURE CopyFile
( SourceRootDir : PathSpec; {005}
CurrentDirectory : DirectoryEntryPtr;
FileInfo : FileEntryPtr;
DestinRootDir : PathSpec); {005}
(***********************************************************************
FUNCTIONAL DESCRIPTION:
Duplicates the source file on the destination. This duplication al-{005}
ways includes relative directory and file name, and file attributes.{005}
In the case of non-directory files, this also includes modification{005}
date and time, and contents. {005}
FORMAL PARAMETERS:
SourceRootDirectory - A PathSpec expression giving the root direct-{005}
ory to which DirectoryName is a relative directory for the{005}
source file. {005}
CurrentDirectory - A DirectoryEntryPtr pointing to a DirectoryEntry
object describing the directory in which the source file resides
and in which the target file is to reside.
FileInfo - A FileEntryPtr pointing to a FileEntry object describing
the source file, and which is to describe the target file.
TargetRootDirectory - A PathSpec expression giving the root direct-{005}
ory to which DirectoryName is a relative directory for the tar-{005}
get file. {005}
RETURN VALUE:
None.
IMPLICIT INPUTS:
Logging - The BOOLEAN telling whether event logging is currently on.
IMPLICIT OUTPUTS:
None.
SIDE EFFECTS:
None.
***********************************************************************)
CONST
BufferSize = 1024;
VAR
CopyBuffer : ARRAY [1..BufferSize] OF CHAR;
DestinHandle : INTEGER;
DestinName : PathSpec;
Registers : RegPack;
SourceHandle : INTEGER;
SourceName : PathSpec;
TransferSize : INTEGER;
BEGIN
(*
* Construct the source and destination file names
*)
SourceName := ConstructFileName (SourceRootDir, {005}
CurrentDirectory ^. Name, FileInfo ^. Name); {005}
DestinName := ConstructFileName (DestinRootDir, {005}
CurrentDirectory ^. Name, FileInfo ^. Name); {005}
SourceName [Length (SourceName) + 1] := #$00;
DestinName [Length (DestinName) + 1] := #$00;
(*
* Now copy the files
*)
IF (FileInfo ^. Attr AND DirectoryAttrMask) <> 0
THEN
BEGIN
(*
* For a directory, simply create the target directory
*)
Registers . AH := DOSFunctionCreateSubDirectory;
Registers . DS := Seg (DestinName [1]);
Registers . DX := Ofs (DestinName [1]);
MsDos (Registers);
IF ErrorReturn (Registers) {004}
THEN
BEGIN
WriteLn ('Cannot create directory ', DestinName);
Halt
END
END
ELSE
BEGIN
(*
* For a file, copy the data and set the creation date and time
*)
Registers . AH := DOSFunctionOpenFile;
Registers . AL := 0;
Registers . DS := Seg (SourceName [1]);
Registers . DX := Ofs (SourceName [1]);
MsDos (Registers);
IF ErrorReturn (Registers) {004}
THEN
BEGIN
WriteLn ('Cannot open ', SourceName);
Halt
END;
SourceHandle := Registers . AX;
Registers . AH := DOSFunctionCreateFile;
Registers . CX := 0;
Registers . DS := Seg (DestinName [1]);
Registers . DX := Ofs (DestinName [1]);
MsDos (Registers);
IF ErrorReturn (Registers) {004}
THEN
BEGIN
WriteLn ('Cannot create ', DestinName);
Halt
END;
DestinHandle := Registers . AX;
Registers . AH := DOSFunctionReadFromFile;
Registers . BX := SourceHandle;
Registers . CX := BufferSize;
Registers . DS := Seg (CopyBuffer);
Registers . DX := Ofs (CopyBuffer);
MsDos (Registers);
IF ErrorReturn (Registers) {004}
THEN
BEGIN
WriteLn ('Cannot read ', SourceName);
Halt
END;
TransferSize := Registers . AX;
WHILE TransferSize > 0
DO
BEGIN
Registers . AH := DOSFunctionWriteToFile;
Registers . BX := DestinHandle;
Registers . CX := TransferSize;
Registers . DS := Seg (CopyBuffer);
Registers . DX := Ofs (CopyBuffer);
MsDos (Registers);
IF ErrorReturn (Registers) OR {004}
(TransferSize <> Registers . AX)
THEN
BEGIN
WriteLn ('Cannot write ', DestinName);
Halt
END;
Registers . AH := DOSFunctionReadFromFile;
Registers . BX := SourceHandle;
Registers . CX := BufferSize;
Registers . DS := Seg (CopyBuffer);
Registers . DX := Ofs (CopyBuffer);
MsDos (Registers);
IF ErrorReturn (Registers) {004}
THEN
BEGIN
WriteLn ('Cannot read ', SourceName);
Halt
END;
TransferSize := Registers . AX
END;
(*
* The data have been copied. Set the creation date and time
* to be that of the source file.
*)
Registers . AH := DOSFunctionSetFileDateTime;
Registers . AL := 1;
Registers . BX := DestinHandle;
Registers . CX := FileInfo ^. Time;
Registers . DX := FileInfo ^. Date;
MsDos (Registers);
IF ErrorReturn (Registers) {004}
THEN
BEGIN
WriteLn ('Cannot set date and time on ', DestinName);
Halt
END;
(*
* Close the source and destination files
*)
Registers . AH := DOSFunctionCloseFile;
Registers . BX := SourceHandle;
MsDos (Registers);
IF ErrorReturn (Registers) {004}
THEN
BEGIN
WriteLn ('Cannot close ', SourceName);
Halt
END;
Registers . AH := DOSFunctionCloseFile;
Registers . BX := DestinHandle;
MsDos (Registers);
IF ErrorReturn (Registers) {004}
THEN
BEGIN
WriteLn ('Cannot close ', DestinName);
Halt
END
END;
(*
* Ensure that the source and target attributes match
*)
IF (FileInfo ^. Attr AND NOT DirectoryAttrMask) <> 0
THEN
BEGIN
Registers . AH := DOSFunctionChangeAttributes;
Registers . AL := 1;
Registers . DS := Seg (DestinName [1]);
Registers . DX := Ofs (DestinName [1]);
Registers . CX := FileInfo ^. Attr;
MsDos (Registers);
IF ErrorReturn (Registers) {004}
THEN
BEGIN
WriteLn ('Cannot set attributes for ', DestinName);
Halt
END
END;
(*
* If necessary, log the copying
*)
IF Logging
THEN
IF (FileInfo ^. Attr AND DirectoryAttrMask) <> 0
THEN
WriteLn ('Created directory ', DestinName)
ELSE
WriteLn ('Copied ', SourceName, ' to ', DestinName)
END;
{.PA}
PROCEDURE ReplaceFile
( SourceRootDir : PathSpec; {005}
CurrentDirectory : DirectoryEntryPtr;
SourceFile : FileEntryPtr;
DestinRootDir : PathSpec; {005}
DestinFile : FileEntryPtr);
(***********************************************************************
FUNCTIONAL DESCRIPTION:
Replaces a file on the destination drive with one of the same path
specification from the source drive.
FORMAL PARAMETERS:
SourceRootDirectory - A PathSpec expression giving the root direct-{005}
ory to which DirectoryName is a relative directory for the{005}
source file. {005}
CurrentDirectory - A DirectoryEntryPtr expression pointing to a
DirectoryEntry object describing the directory in which the
source and destination files are found.
SourceFile - A FileEntryPtr expression pointing to a FileEntry
object describing the source file.
TargetRootDirectory - A PathSpec expression giving the root direct-{005}
ory to which DirectoryName is a relative directory for the tar-{005}
get file. {005}
DestinationFile - A FileEntryPtr expression pointing to a FileEntry
object describing the destination file.
RETURN VALUE:
None.
IMPLICIT INPUTS:
None.
IMPLICIT OUTPUTS:
None.
SIDE EFFECTS:
None.
***********************************************************************)
BEGIN
(*
* For directories, nothing need be done
*)
IF ((SourceFile ^. Attr AND DirectoryAttrMask) = 0) OR
((DestinFile ^. Attr AND DirectoryAttrMask) = 0)
THEN
BEGIN
(*
* At least one is a file. Delete the existing thing, and copy
* the new thing
*)
DeleteFile (DestinRootDir, CurrentDirectory, DestinFile); {005}
CopyFile (SourceRootDir, CurrentDirectory, SourceFile, {005}
DestinRootDir) {005}
END
END;
{.PA}
PROCEDURE MatchFile
( SourceRootDir : PathSpec; {005}
CurrentDirectory : DirectoryEntryPtr;
SourceFile : FileEntryPtr;
DestinRootDir : PathSpec; {005}
DestinFile : FileEntryPtr);
(***********************************************************************
FUNCTIONAL DESCRIPTION:
Modifies the non-directory attributes of a destination file to dup-
licate those of a source file.
FORMAL PARAMETERS:
SourceRootDirectory - A PathSpec expression giving the root direct-{005}
ory to which DirectoryName is a relative directory for the{005}
source file. {005}
CurrentDirectory - A DirectoryEntryPtr expression pointing to a Dir-
ectoryEntry object describing the directory in which the destin-
ation file is to be found.
SourceFile - A FileEntryPtr expression pointing to a FileEntry ob-
ject describing the source file.
TargetRootDirectory - A PathSpec expression giving the root direct-{005}
ory to which DirectoryName is a relative directory for the tar-{005}
get file. {005}
DestinationFile - A FileEntryPtr expression pointing to a FileEntry
object describing the destination file.
RETURN VALUE:
None.
IMPLICIT INPUTS:
Logging - The BOOLEAN telling whether event logging is currently on.
IMPLICIT OUTPUTS:
None.
SIDE EFFECTS:
None.
***********************************************************************)
VAR
DestinName : PathSpec;
Registers : RegPack;
BEGIN
(*
* Ensure the attributes match
*)
IF SourceFile ^. Attr <> DestinFile ^. Attr
THEN
BEGIN
(*
* Copy attributes from the source to the destination
*)
DestinName := ConstructFileName (TargetRoot, {005}
CurrentDirectory ^. Name, DestinFile ^. Name); {005}
DestinName [Length (DestinName) + 1] := #$00;
Registers . AH := DOSFunctionChangeAttributes;
Registers . AL := 1;
Registers . DS := Seg (DestinName [1]);
Registers . DX := Ofs (DestinName [1]);
Registers . CX := SourceFile ^. Attr AND NOT DirectoryAttrMask; {001}
MsDos (Registers);
IF ErrorReturn (Registers) {004}
THEN
BEGIN
WriteLn ('Cannot change attributes on ', DestinName);
Halt
END;
(*
* If logging, note the change
*)
IF Logging
THEN
WriteLn ('Modified attributes of ', DestinName)
END
END;
{.PA}
(***********************************************************************
FUNCTIONAL DESCRIPTION:
Modifies a target volume to duplicate as closely as possible a
source volume.
COMMAND LINE:
<SourceRoot> <TargetRoot> [/[NO]LOG] [/[NO]ACCUMULATE] {009}
RETURN VALUE:
None.
IMPLICIT INPUTS:
SourceRoot - The root directory of the source directory tree. {009}
TargetRoot - The root directory of the target directory tree. {009}
Accumulating - The BOOLEAN telling whether files on the target are{009}
to be retained if they are not on the source. {009}
IMPLICIT OUTPUTS:
None.
SIDE EFFECTS:
None.
***********************************************************************)
VAR
CurrentDirectory : DirectoryEntryPtr;
DestinDirectory : FileEntryQueue;
PendingDirectories : DirectoryEntryPtr;
SourceDirectory : FileEntryQueue;
BEGIN
(*
* Print the copyright notice
*)
WriteLn ('TREEDUPL version ', VersionIdentification); {008,002}
WriteLn;
(* {009}
* Parse the command line {009}
*) {009}
ParseCommandLine; {009}
(*
* Initialize the directory needing duplication to be the root
*)
GetMem (PendingDirectories, DirectoryEntrySize); {005}
PendingDirectories ^. Next := NIL;
PendingDirectories ^. Name := ''; {005}
(*
* Copy the directories on the pending directory list
*)
WHILE PendingDirectories <> NIL
DO
BEGIN
CurrentDirectory := PendingDirectories;
PendingDirectories := PendingDirectories ^. Next;
(*
* Expand directories on the two volumes
*)
ExpandDirectory (SourceRoot, CurrentDirectory, SourceDirectory); {005}
ExpandDirectory (TargetRoot, CurrentDirectory, DestinDirectory); {005}
(*
* Extract the directories from the source listing
*)
ExtractDirectories (CurrentDirectory, SourceDirectory,
PendingDirectories);
(*
* Ensure that the contents of the source and destination direct-
* ories match
*)
WHILE (SourceDirectory . Head <> NIL) OR
(DestinDirectory . Head <> NIL)
DO
BEGIN
IF SourceDirectory . Head = NIL
THEN
BEGIN
(*
* The source directory has been exhausted before the
* destination directory. Delete the destination directory
* file if not accumulating files. {003}
*)
IF NOT Accumulating {003}
THEN {003}
DeleteFile (TargetRoot, CurrentDirectory, {005}
DestinDirectory . Head);
AdvanceFile (DestinDirectory)
END
ELSE IF DestinDirectory . Head = NIL
THEN
BEGIN
(*
* The destination directory has been exhausted before the
* source directory. Copy the file.
*)
CopyFile (SourceRoot, CurrentDirectory, {005}
SourceDirectory . Head, TargetRoot); {005}
AdvanceFile (SourceDirectory)
END
ELSE IF SourceDirectory . Head ^. Name <
DestinDirectory . Head ^. Name
THEN
BEGIN
(*
* The destination directory does not have a file of the
* same name as the file in the source directory. Copy the
* file.
*)
CopyFile (SourceRoot, CurrentDirectory, {005}
SourceDirectory . Head, TargetRoot); {005}
AdvanceFile (SourceDirectory)
END
ELSE IF SourceDirectory . Head ^. Name >
DestinDirectory . Head ^. Name
THEN
BEGIN
(*
* The destination directory has a file whose name is not
* in the source directory. Delete the destinatin file if{003}
* not accumulating files. {003}
*)
IF NOT Accumulating
THEN
DeleteFile (TargetRoot, CurrentDirectory, {005}
DestinDirectory . Head);
AdvanceFile (DestinDirectory)
END
ELSE IF NOT IDAttrMatch (SourceDirectory . Head,
DestinDirectory . Head)
THEN
BEGIN
(*
* The source and destination directories have files of the
* same name, but the identity attributes do not match.
* Delete the file in the destination directory, and copy
* the file from the source directory.
*)
ReplaceFile (SourceRoot, CurrentDirectory, {005}
SourceDirectory . Head, TargetRoot, {005}
DestinDirectory . Head);
AdvanceFile (SourceDirectory);
AdvanceFile (DestinDirectory)
END
ELSE
BEGIN
(*
* The source and destination directories have files of the
* same name and the identity attributes match. Make the
* MS-DOS file attributes match.
*)
MatchFile (SourceRoot, CurrentDirectory, {005}
SourceDirectory . Head, TargetRoot, {005}
DestinDirectory . Head);
AdvanceFile (SourceDirectory);
AdvanceFile (DestinDirectory)
END
END;
(*
* The current directory has been handled.
*)
FreeMem (CurrentDirectory, DirectoryEntrySize +
Length (CurrentDirectory ^. Name));
END
END.